/**
 * Representation du monde. Celui-ci existe a une temperature
 * et une pression donnee.
 *
 * Le monde contient aussi trois Array() qui permettent
 * de stocker les differents objets Cellules, Molecules et Ccs
 * presents dans le monde.
 *
 * @param {Number} temprature Temperature moyenne sur la fenetre
 * @param {Number} tempo Temporisation de l'application
 * @param {Number} pression Valeur moyenne de la pression
 */
function Monde(temperature, tempo, pression){
    /**
     * Nombre de tours passés dans le jeu
     */
    this.nbTours = 0;
    /*
     * Penser pour la suppression a appeler d'abord les Ccs puis Molecules
     * puis Cellules. Il faudra aussi pour l'affichage appeler d'abord
     * Cellule, puis Molecule, puis enfin Cc pour que les plus gros objets
     * soient en dessous des plus petits.
     */
    // Declaration des constantes
    /**
     * Tolerance pour la distance de suppression d'une cellule.
     *
     * L'utilisateur pourra supprimer la cellule si celle-ci est
     * plus proche que le nombre indique
     *
     * @type {Number}
     */
    this.TOLERANCE_SUPPRESSION_CELLULE = 10;
    /**
     * Tolerance pour la distance de suppression d'une molecule.
     *
     * L'utilisateur pourra supprimer la molecule si celle-ci est
     * plus proche que le nombre indique
     *
     * @type {Number}
     */
    this.TOLERANCE_SUPPRESSION_MOLECULE = 5;
    /**
     * Tolerance pour la distance de suppression d'un cc.
     *
     * L'utilisateur pourra supprimer le cc si celui-ci est
     * plus proche que le nombre indique
     *
     * @type {Number}
     */
    this.TOLERANCE_SUPPRESSION_CC = 3;
    
    // Declaration des variables
    /**
     * Temperature qui reigne sur le Monde
     *
     * @type {Float}
     */
    this.temperature = temperature;
    this.tempo = tempo;
    /**
     * Pression courante sur la carte
     *
     * @type {Float}
     */
    this.pression = pression;
    /**
     * Tableau qui reference les cellules sur le Monde
     *
     * @type {Array}
     */
    this.tabCellule = new Array();
    /**
     * Tableau qui reference les molecules sur le Monde
     *
     * @type {Array}
     */
    this.tabMolecule = new Array();
    /**
     * Tableau qui reference les ccs sur le Monde
     *
     * @type {Array}
     */
    this.tabCc = new Array();
    
    /**
     * Methode qui appelle dans l'ordre les methodes pour supprimer un
     * cc, une molecule ou une cellule. Des qu'une suppression a ete
     * effectuee, alors la methode s'arrete et retourne vrai. Si aucune
     * entite n'a ete supprimer alors la methode retourne faux.
     *
     * La methode cherche d'abord a supprimer un objet aux coordonnes
     * exactes. S'il n'y en a aucun, alors on supprimera l'objet selon
     * la tolerance accordee dans les constantes de classe.
     *
     * @param {Object} xSupp Abscisse de l'entite a supprimer
     * @param {Object} ySupp Ordonnee de l'entite a supprimer
     * @return {Boolean} Indique si la suppression a eu lieu
     */
    this.supprimer = function(xSupp, ySupp){
        if (this.supprimerCc(xSupp, ySupp)) 
            return true;
        else 
            if (this.supprimerMolecule(xSupp, ySupp)) 
                return true;
            else 
                if (this.supprimerCellule(xSupp, ySupp)) 
                    return true;
        return false;
    };
    
    /**
     * Permet de supprimer une cellule du monde grace a ses coordonnees
     *
     * S'il n'existe aucune cellule aux coordonnees indiquees alors on
     * supprime la plus proche a condition qu'elle ne soit pas a une
     * distance superieure a la valeur TOLERANCE_SUPPRESSION_CELLULE
     *
     * @param {Number} xSupp Abscisse de la cellule
     * @param {Number} ySupp Ordonnee de la cellule
     */
    this.supprimerCellule = function(xSupp, ySupp){
        var valeur_min = Math.pow(2, 53);
        var indice_min = -1;
        for (var i = this.tabCellule.length - 1; i >= 0; i--) {
            if (this.tabCellule[i].x == xSupp && this.tabCellule[i].y == ySupp) {
                this.tabCellule.splice(i, 1);
                return true;
            }
            if (this.tabCellule[i].distance(xSupp, ySupp) < valeur_min) {
                valeur_min = this.tabCellule[i].distance(xSupp, ySupp);
                indice_min = i;
            }
        }
        if (valeur_min <= this.TOLERANCE_SUPPRESSION_CELLULE) {
            this.tabCellule.splice(indice_min, 1);
            return true;
        }
        return false;
    };
    
    /**
     * Permet de supprimer une molecule du monde grace a ses coordonnees
     *
     * S'il n'existe aucune molecule aux coordonnes indiquees alors on
     * supprime la plus proche a condition qu'elle ne soit pas a une
     * distance superieure a la valeur TOLERANCE_SUPPRESSION_MOLECULE
     *
     * @param {Number} xSupp Abscisse de la molecule a supprimer
     * @param {Number} ySupp Ordonnee de la molecule a supprimer
     * @return {Boolean} Indique si la suppression a eu lieu
     */
    this.supprimerMolecule = function(xSupp, ySupp){
        var valeur_min = Math.pow(2, 53);
        var indice_min = -1;
        for (var i = this.tabMolecule.length - 1; i >= 0; i--) {
            if (this.tabMolecule[i].x == xSupp && this.tabMolecule[i].y == ySupp) {
                this.tabMolecule.splice(i, 1);
                return true;
            }
            if (this.tabMolecule[i].distance(xSupp, ySupp) < valeur_min) {
                valeur_min = this.tabMolecule[i].distance(xSupp, ySupp);
                indice_min = i;
            }
        }
        if (valeur_min <= this.TOLERANCE_SUPPRESSION_MOLECULE) {
            this.tabMolecule.splice(indice_min, 1);
            return true;
        }
        return false;
    };
    
    /**
     * Permet de supprimer un cc du monde grace a ses coordonnees
     *
     * S'il n'existe aucun cc aux coordonnes indiquees alors on
     * supprime le plus proche a condition qu'il ne soit pas a une
     * distance superieure a la valeur TOLERANCE_SUPPRESSION_CC
     *
     * @param {Number} xSupp Abscisse du cc a supprimer
     * @param {Number} ySupp Ordonnee du cc a supprimer
     * @return {Boolean} Indique si la suppression a eu lieu
     */
    this.supprimerCc = function(xSupp, ySupp){
        var valeur_min = Math.pow(2, 53);
        var indice_min = -1;
        for (var i = this.tabCc.length - 1; i >= 0; i--) {
            if (this.tabCc[i].x == xSupp && this.tabCc[i].y == ySupp) {
                this.tabCc.splice(i, 1);
                return true;
            }
            if (this.tabCc[i].distance(xSupp, ySupp) < valeur_min) {
                valeur_min = this.tabCc[i].distance(xSupp, ySupp);
                indice_min = i;
            }
        }
        if (valeur_min <= this.TOLERANCE_SUPPRESSION_CC) {
            this.tabCc.splice(indice_min, 1);
            return true;
        }
        return false;
    };
    
    this.ajouterCellule = function(cellule){
        this.tabCellule.push(cellule);
        this.tabCellule[this.tabCellule.length - 1].supportePression(this, this.pression);
        this.tabCellule[this.tabCellule.length - 1].supporteTemperature(this, this.temperature);
        this.tabCellule[this.tabCellule.length - 1].ajusteEtatSante();
    };
    
    /**
     * Methode qui permet d'ajouter une entite en fonction de son nom.
     * La methode regarde le nom de l'entite et creer l'objet du bon type,
     * et l'ajoute dans la bonne Array.
     *
     * @param {Object} nomEntite Nom de l'entite a ajouter
     * @param {Object} x Abscisse ou ajouter l'entite
     * @param {Object} y Ordonnee ou ajouter l'entute
     */
    this.ajouter = function(nomEntite, x, y){
        switch (nomEntite) {
            case 'Brasseryte':
            case 'Kommyte':
            case 'Polakocyte':
            case 'Samuelocyte':
            case 'Zimbabwyte':
                this.tabCellule.push(new Cellule(nomEntite, x, y));
                this.tabCellule[this.tabCellule.length - 1].supportePression(this, this.pression);
                this.tabCellule[this.tabCellule.length - 1].supporteTemperature(this, this.temperature);
                this.tabCellule[this.tabCellule.length - 1].ajusteEtatSante();
                break;
            case 'Bretoine':
            case 'Carottoine':
            case 'Haricoine':
            case 'Kiwine':
            case 'Mongoloine':
            case 'Oppiomine':
            case 'Pommine':
                this.tabMolecule.push(new Molecule(nomEntite, x, y));
                break;
            case 'Acetone':
            case 'Acide sulfurique':
            case 'Benzene':
            case 'Eau':
            case 'Ethanol':
                this.tabCc.push(new Cc(nomEntite, x, y));
                break;
            default:
                alert('Entite inconnue !');
                break;
        }
    };
    
    /**
     * Methode qui retourne l'element le plus proche de la cellule
     * dans la limite de son rayon de recherche, et dont elle a le
     * plus besoin !
     *
     * @param {Number} xCell Abscisse de la cellule
     * @param {Number} yCell Ordonnee de la cellule
     * @param {Number} rayon Rayon de recherche de la cellule
     * @param {Object} tEnt Type d'entitee recherchee
     * @param {Object} nEnt Nom de l'entitee recherchee
     * @return {Object} Element le plus proche de la cellule
     */
    this.positionBesoinMax = function(xCell, yCell, rCell, tEnt, nEnt){
        //TODO : Amelioration ulterieure possible : tableau associatif
        //multidimensionnel permettant de sauvegarder tous les elements
        //a une certaine distance et permettant donc a la cellule de se
        //diriger vers celui qui l'interesse et qui est le plus proche
        //OU encore retourner une Array de chaque type d'elements le plus
        //proche de la cellule qui evitera plusieurs parcours inutiles du
        //monde
        var iFinal = -1;
        var distanceTmp = -1;
        var distanceMin = Math.pow(2, 53);
        if (tEnt == "Cc") {
            for (var i = 0; i < this.tabCc.length; i++) {
                distanceTmp = this.tabCc[i].distance(xCell, yCell);
                if (this.tabCc[i].nom == nEnt && distanceTmp <= rCell && distanceMin > distanceTmp) {
                    iFinal = i;
                    distanceMin = distanceTmp;
                }
            }
            return this.tabCc[iFinal];
        }
        else {
            for (var i = 0; i < this.tabMolecule.length; i++) {
                distanceTmp = this.tabMolecule[i].distance(xCell, yCell);
                if (this.tabMolecule[i].nom == nEnt && distanceTmp <= rCell && distanceMin > distanceTmp) {
                    iFinal = i;
                    distanceMin = distanceTmp;
                }
            }
            return this.tabMolecule[iFinal];
        }
        return null;
    };
    
    /**
     * Analyse si la cellule ciblee va mourir de surpopulation ou non. Si elle
     * est entouree de cellules (4 dans son aire) de type different de celle-ci,
     * ou si le nombre de cellules alentour de tous types depasse la tolerance
     * que possede la cellule, alors elle se trouve dans une situation de
     * surpopulation.
     *
     * @param {String} nomCell Type de la cellule
     * @param {Number} xCell Abscisse de la cellule
     * @param {Number} yCell Ordonnee de la cellule
     * @param {Number} tailleCell Taille de la cellule
     * @param {Number} tolerancePop Nombre maximal de voisins que peut supporter
     * une cellule
     * @return {Boolean} Retourne vrai si la cellule dans un cas de
     * surpopulation, faux sinon.
     */
    this.surpeuple = function(nomCell, xCell, yCell, tailleCell, tolerancePop){
        var compteur = 0;
        var compteurGlobal = 0;
        for (var i = 0; i < this.tabCellule.length; i++) {
            if (this.tabCellule[i].distance(xCell, yCell) < tailleCell) {
                if (this.tabCellule[i].nom != nomCell) 
                    compteur++;
                compteurGlobal++;
            }
        }
        if (compteur >= 4) 
            return true;
        else 
            if (compteurGlobal > tolerancePop) 
                return true;
            else 
                return false;
    };
    
    /**
     * Methode qui permet a une cellule de savoir si une cellule cancerigene
     * est presente dans son rayon de recherche.
     *
     * @param {Object} xCell Abscisse de la cellule
     * @param {Object} yCell Ordonnee de la cellule
     * @param {Object} rayon Rayon de recherche de la cellule
     * @return {Object} Premiere cellule ennemie a portee ou null s'il n'y
     * en a pas
     */
    this.getEnnemi = function(xCell, yCell, rayon){
        for (var i = 0; i < this.tabCellule.length; i++) {
            if (this.tabCellule[i].distance(xCell, yCell) <= rayon &&
            this.tabCellule[i].nom == "Polakocyte") 
                return this.tabCellule[i];
        }
        return null;
    };
    
    /**
     * Methode qui retourne une array des Molecules assimilables par la
     * cellule, c'est a dire celles qui se trouvent dans l'aire qu'elle
     * recouvre
     *
     * @param {Object} xCell Abscisse de la cellule
     * @param {Object} yCell Ordonnee de la cellule
     * @param {Object} limite Taille de la cellule
     * @return {Array} Liste des Moleculess assimilables
     */
    this.getNourritureMol = function(xCell, yCell, limite){
        var res = new Array();
        for (var i = 0; i < this.tabMolecule.length; i++) {
            if (this.tabMolecule[i].distance(xCell, yCell) < limite) 
                res.push(this.tabMolecule[i]);
        }
        return res;
    };
    
    /**
     * Methode qui retourne une array des Ccs assimilables par la
     * cellule, c'est a dire ceux qui se trouvent dans l'aire qu'elle
     * recouvre
     *
     * @param {Object} xCell Abscisse de la cellule
     * @param {Object} yCell Ordonnee de la cellule
     * @param {Object} limite Taille de la cellule
     * @return {Array} Liste des Ccs assimilables
     */
    this.getNourritureCc = function(xCell, yCell, limite){
        var res = new Array();
        for (var i = 0; i < this.tabCc.length; i++) {
            if (this.tabCc[i].distance(xCell, yCell) < limite) 
                res.push(this.tabCc[i]);
        }
        return res;
    };
    
    /**
     * Retourne une cible a la cellule qui appelle la methode. La
     * cible ne peut pas etre une cellule cancereuse.
     *
     * @param {Number} xCancer Abscisse de la cellule cancereuse
     * @param {Number} yCancer Ordonnee de la cellule cancereuse
     * @param {Number} taille Taille de la cellule cancereuse
     * @return {Object} Une cellule cible s'il y en a, null sinon.
     */
    this.getCible = function(xCancer, yCancer, taille){
        for (var i = 0; i < this.tabCellule.length; i++) {
            if (this.tabCellule[i].nom != "Polakocyte") 
                if (this.tabCellule[i].distance(xCancer, yCancer) < taille) 
                    return this.tabCellule[i];
        }
        return null;
    };
    
    /**
     * Methode qui permet de mettre a jour la pression du monde avec
     * la nouvelle valeur du slider, et de verifier si les cellules du
     * monde supportent cette nouvelle valeur
     *
     * @param {Object} nouvellePression Nouvelle pression du monde
     */
    this.ajustePression = function(nouvellePression){
        this.pression = nouvellePression;
        for (var i = 0; i < this.tabCellule.length; i++) {
            this.tabCellule[i].supportePression(this, this.pression);
        }
    }
    
    /**
     * Methode qui permet de mettre a jour la temperature du monde avec
     * la nouvelle valeur du slider, et de verifier si les cellules du
     * monde supportent cette nouvelle valeur
     *
     * @param {Object} nouvelleTemperature Nouvelle temperature du monde
     */
    this.ajusteTemperature = function(nouvelleTemperature){
        this.temperature = nouvelleTemperature;
        for (var i = 0; i < this.tabCellule.length; i++) {
            this.tabCellule[i].supporteTemperature(this, parseInt(this.temperature));
        }
    }
    
    /**
     * Fonction qui permet d'actualiser la couleur de 10% des cellules du tableau
     */
    this.actualiseCouleurCellules = function(){
        for (var i = Math.floor(((this.nbTours % 10) * this.tabCellule.length) / 10); i < Math.round((((this.nbTours % 10) + 1) * this.tabCellule.length) / 10); i++) {
            this.tabCellule[i].ajusteEtatSante();
        }
    }
    
    /**
     * Methode appelee a chaque tour et permet d'appeler les methodes
     * d'evolution sur chaque objet
     */
    this.evolution = function(){
        this.actualiseCouleurCellules();
        for (var i = 0; i < this.tabCc.length; i++) {
            this.tabCc[i].evolue();
        }
        for (var i = 0; i < this.tabMolecule.length; i++) {
            this.tabMolecule[i].evolue(this);
        }
        for (var i = 0; i < this.tabCellule.length; i++) {
            this.tabCellule[i].evolue(this);
        }
        this.nbTours++;
    };
}
